home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 2003 August / MW 8 2003 CD1.iso / Inside Macworld / Product News / gimp-1.2.4.sit / gimp-1.2.4 / plug-ins / helpbrowser / helpbrowser.c < prev    next >
Encoding:
C/C++ Source or Header  |  2001-07-02  |  31.0 KB  |  1,265 lines

  1. /* The GIMP -- an image manipulation program
  2.  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
  3.  *
  4.  * The GIMP Help Browser
  5.  * Copyright (C) 1999 Sven Neumann <sven@gimp.org>
  6.  *                    Michael Natterer <mitschel@cs.tu-berlin.de>
  7.  *
  8.  * Some code & ideas stolen from the GNOME help browser.
  9.  *
  10.  * This program is free software; you can redistribute it and/or modify
  11.  * it under the terms of the GNU General Public License as published by
  12.  * the Free Software Foundation; either version 2 of the License, or
  13.  * (at your option) any later version.
  14.  *
  15.  * This program is distributed in the hope that it will be useful,
  16.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18.  * GNU General Public License for more details.
  19.  *
  20.  * You should have received a copy of the GNU General Public License
  21.  * along with this program; if not, write to the Free Software
  22.  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  23.  */
  24. #include "config.h"
  25.  
  26. #include <string.h> 
  27. #include <stdio.h>
  28. #include <stdlib.h>
  29. #include <unistd.h>
  30.  
  31. #include <gtk/gtk.h>
  32. #include <gdk/gdkkeysyms.h>
  33. #include <gtk-xmhtml/gtk-xmhtml.h>
  34.  
  35. #include <libgimp/gimp.h>
  36. #include <libgimp/gimpui.h>
  37.  
  38. #include "queue.h"
  39.  
  40. #include "libgimp/stdplugins-intl.h"
  41.  
  42. #include "forward.xpm"
  43. #include "back.xpm"
  44.  
  45.  
  46. /*  defines  */
  47.  
  48. #ifdef __EMX__
  49. #define chdir _chdir2
  50. #endif
  51.  
  52. #define GIMP_HELP_EXT_NAME      "extension_gimp_help_browser"
  53. #define GIMP_HELP_TEMP_EXT_NAME "extension_gimp_help_browser_temp"
  54.  
  55. #define GIMP_HELP_PREFIX        "help"
  56.  
  57. enum {
  58.   CONTENTS,
  59.   INDEX,
  60.   HELP
  61. };
  62.  
  63. enum {
  64.   URL_UNKNOWN,
  65.   URL_NAMED, /* ??? */
  66.   URL_JUMP,
  67.   URL_FILE_LOCAL,
  68.  
  69.   /* aliases */
  70.   URL_LAST = URL_FILE_LOCAL
  71. };
  72.  
  73. /*  structures  */
  74.  
  75. typedef struct 
  76. {
  77.   gint       index;
  78.   gchar     *label;
  79.   Queue     *queue;
  80.   gchar     *current_ref;
  81.   GtkWidget *html;
  82.   gchar     *home;
  83. } HelpPage;
  84.  
  85. typedef struct
  86. {
  87.   gchar *title;
  88.   gchar *ref;
  89.   gint   count;
  90. } HistoryItem;
  91.  
  92. /*  constant strings  */
  93.  
  94. static gchar *doc_not_found_format_string =
  95. N_("<html><head><title>Document not found</title></head>"
  96.    "<body bgcolor=\"#ffffff\">"
  97.    "<center>"
  98.    "<p>"
  99.    "%s"
  100.    "<h3>Couldn't find document</h3>"
  101.    "<tt>%s</tt>"
  102.    "</center>"
  103.    "<p>"
  104.    "<small>This either means that the help for this topic has not been written "
  105.    "yet or that something is wrong with your installation. "
  106.    "Please check carefully before you report this as a bug.</small>" 
  107.    "</body>"
  108.    "</html>");
  109.  
  110. static gchar *dir_not_found_format_string =
  111. N_("<html><head><title>Directory not found</title></head>"
  112.    "<body bgcolor=\"#ffffff\">"
  113.    "<center>"
  114.    "<p>"
  115.    "%s"
  116.    "<h3>Couldn't change to directory</h3>"
  117.    "<tt>%s</tt>"
  118.    "<h3>while trying to access</h3>"
  119.    "<tt>%s</tt>"
  120.    "</center>"
  121.    "<p>"
  122.    "<small>This either means that the help for this topic has not been written "
  123.    "yet or that something is wrong with your installation. "
  124.    "Please check carefully before you report this as a bug.</small>" 
  125.    "</body>"
  126.    "</html>");
  127.  
  128. static gchar *eek_png_tag = "<h1>Eeek!</h1>";
  129.  
  130.  
  131. /*  the three help notebook pages  */
  132.  
  133. static HelpPage pages[] =
  134. {
  135.   {
  136.     CONTENTS,
  137.     N_("Contents"),
  138.     NULL,
  139.     NULL,
  140.     NULL,
  141.     "contents.html"
  142.   },
  143.  
  144.   {
  145.     INDEX,
  146.     N_("Index"),
  147.     NULL,
  148.     NULL,
  149.     NULL,
  150.     "index.html"
  151.   },
  152.  
  153.   {
  154.     HELP,
  155.     NULL,
  156.     NULL,
  157.     NULL,
  158.     NULL,
  159.     "introduction.html"
  160.   }
  161. };
  162.  
  163. static gchar     *gimp_help_root = NULL;
  164.  
  165. static HelpPage  *current_page = &pages[HELP];
  166. static GList     *history = NULL;
  167.  
  168. static GtkWidget *back_button;
  169. static GtkWidget *forward_button;
  170. static GtkWidget *notebook;
  171. static GtkWidget *combo;
  172.  
  173. static GtkTargetEntry help_dnd_target_table[] =
  174. {
  175.   { "_NETSCAPE_URL", 0, 0 },
  176. };
  177. static guint n_help_dnd_targets = (sizeof (help_dnd_target_table) /
  178.                    sizeof (help_dnd_target_table[0]));
  179.  
  180. /*  GIMP plugin stuff  */
  181.  
  182. static void query (void);
  183. static void run   (gchar      *name,
  184.            gint        nparams,
  185.            GimpParam  *param,
  186.            gint       *nreturn_vals,
  187.            GimpParam **return_vals);
  188.  
  189. GimpPlugInInfo PLUG_IN_INFO =
  190. {
  191.   NULL,  /* init_proc  */
  192.   NULL,  /* quit_proc  */
  193.   query, /* query_proc */
  194.   run,   /* run_proc   */
  195. };
  196.  
  197. static gboolean temp_proc_installed = FALSE;
  198.  
  199. /*  forward declaration  */
  200.  
  201. static gint load_page (HelpPage *source_page,
  202.                HelpPage *dest_page,
  203.                gchar    *ref,
  204.                gint      pos, 
  205.                gboolean  add_to_queue,
  206.                gboolean  add_to_history);
  207.  
  208. /*  functions  */
  209.  
  210. static void
  211. close_callback (GtkWidget *widget,
  212.         gpointer   user_data)
  213. {
  214.   gtk_main_quit ();
  215. }
  216.  
  217. static void
  218. update_toolbar (HelpPage *page)
  219. {
  220.   if (back_button)
  221.     gtk_widget_set_sensitive (back_button, queue_isprev (page->queue));
  222.   if (forward_button)
  223.     gtk_widget_set_sensitive (forward_button, queue_isnext (page->queue));
  224. }
  225.  
  226. static void
  227. jump_to_anchor (HelpPage *page, 
  228.         gchar    *anchor)
  229. {
  230.   gint pos;
  231.  
  232.   g_return_if_fail (page != NULL && anchor != NULL);
  233.  
  234.   if (*anchor != '#') 
  235.     {
  236.       gchar *a = g_strconcat ("#", anchor, NULL);
  237.       XmHTMLAnchorScrollToName (page->html, a);
  238.       g_free (a);
  239.     }
  240.   else
  241.     XmHTMLAnchorScrollToName (page->html, anchor);
  242.  
  243.   pos = gtk_xmhtml_get_topline (GTK_XMHTML (page->html));
  244.   queue_add (page->queue, page->current_ref, pos);
  245.  
  246.   update_toolbar (page);
  247. }
  248.  
  249. static void
  250. forward_callback (GtkWidget *widget,
  251.           gpointer   data)
  252. {
  253.   gchar *ref;
  254.   gint pos;
  255.  
  256.   if (!(ref = queue_next (current_page->queue, &pos)))
  257.     return;
  258.  
  259.   load_page (current_page, current_page, ref, pos, FALSE, FALSE);
  260.   queue_move_next (current_page->queue);
  261.  
  262.   update_toolbar (current_page);
  263. }
  264.  
  265. static void
  266. back_callback (GtkWidget *widget,
  267.            gpointer   data)
  268. {
  269.   gchar *ref;
  270.   gint pos;
  271.  
  272.   if (!(ref = queue_prev (current_page->queue, &pos)))
  273.     return;
  274.  
  275.   load_page (current_page, current_page, ref, pos, FALSE, FALSE);
  276.   queue_move_prev (current_page->queue);
  277.  
  278.   update_toolbar (current_page);
  279. }
  280.  
  281. static void 
  282. entry_changed_callback (GtkWidget *widget,
  283.             gpointer   data)
  284. {
  285.   GList       *list;
  286.   HistoryItem *item;
  287.   gchar       *entry_text;
  288.   gchar       *compare_text;
  289.   gboolean     found = FALSE;
  290.  
  291.   entry_text = gtk_entry_get_text (GTK_ENTRY (widget));
  292.  
  293.   for (list = history; list && !found; list = list->next)
  294.     {
  295.       item = (HistoryItem *) list->data;
  296.  
  297.       if (item->count)
  298.     compare_text = g_strdup_printf ("%s <%i>",
  299.                     item->title,
  300.                     item->count + 1);
  301.       else
  302.     compare_text = item->title;
  303.  
  304.       if (strcmp (compare_text, entry_text) == 0)
  305.     {
  306.       load_page (&pages[HELP], &pages[HELP], item->ref, 0, TRUE, FALSE);
  307.       found = TRUE;
  308.     }
  309.  
  310.       if (item->count)
  311.     g_free (compare_text);
  312.     }
  313. }
  314.  
  315. static gint
  316. entry_button_press_callback (GtkWidget      *widget,
  317.                  GdkEventButton *bevent,
  318.                  gpointer        data)
  319. {
  320.   if (current_page != &pages[HELP])
  321.     gtk_notebook_set_page (GTK_NOTEBOOK (notebook), HELP);
  322.  
  323.   return FALSE;
  324. }
  325.  
  326. static void
  327. history_add (gchar *ref,
  328.          gchar *title)
  329. {
  330.   GList       *list;
  331.   GList       *found = NULL;
  332.   HistoryItem *item;
  333.   GList       *combo_list = NULL;
  334.   gint         title_found_count = 0;
  335.  
  336.   for (list = history; list && !found; list = list->next)
  337.     {
  338.       item = (HistoryItem *) list->data;
  339.  
  340.       if (strcmp (item->title, title) == 0)
  341.     {
  342.       if (strcmp (item->ref, ref) != 0)
  343.         {
  344.           title_found_count++;
  345.           continue;
  346.         }
  347.  
  348.       found = list;    }
  349.     }
  350.  
  351.   if (found)
  352.     {
  353.       item = (HistoryItem *) found->data;
  354.       history = g_list_remove_link (history, found);
  355.     }
  356.   else
  357.     {
  358.       item = g_new (HistoryItem, 1);
  359.       item->ref = g_strdup (ref);
  360.       item->title = g_strdup (title);
  361.       item->count = title_found_count;
  362.     }
  363.  
  364.   history = g_list_prepend (history, item);
  365.  
  366.   for (list = history; list; list = list->next)
  367.     {
  368.       gchar* combo_title;
  369.  
  370.       item = (HistoryItem *) list->data;
  371.  
  372.       if (item->count)
  373.     combo_title = g_strdup_printf ("%s <%i>",
  374.                        item->title,
  375.                        item->count + 1);
  376.       else
  377.     combo_title = g_strdup (item->title);
  378.  
  379.       combo_list = g_list_prepend (combo_list, combo_title);
  380.     }
  381.  
  382.   combo_list = g_list_reverse (combo_list);
  383.  
  384.   gtk_signal_handler_block_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  385.   gtk_combo_set_popdown_strings (GTK_COMBO (combo), combo_list);
  386. /*    gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), item->title); */
  387.   gtk_signal_handler_unblock_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  388.  
  389.   for (list = combo_list; list; list = list->next)
  390.     g_free (list->data);
  391.  
  392.   g_list_free (combo_list);
  393. }
  394.  
  395. static void
  396. html_source (HelpPage *page,
  397.          gchar    *ref,
  398.          gint      pos,
  399.          gchar    *source, 
  400.          gboolean  add_to_queue,
  401.          gboolean  add_to_history)
  402. {
  403.   gchar *title = NULL;
  404.   
  405.   g_return_if_fail (page != NULL && ref != NULL && source != NULL);
  406.   
  407.   /* Load it up */
  408.   gtk_xmhtml_source (GTK_XMHTML (page->html), source);
  409.   
  410.   gtk_xmhtml_set_topline (GTK_XMHTML(page->html), pos);
  411.   
  412.   if (add_to_queue) 
  413.     queue_add (page->queue, ref, pos);
  414.  
  415.   if (page->index == HELP)
  416.     {
  417.       title = XmHTMLGetTitle (page->html);
  418.       if (!title)
  419.     title = (_("<Untitled>"));
  420.       
  421.       if (add_to_history)
  422.     history_add (ref, title);
  423.  
  424.       gtk_signal_handler_block_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry),
  425.                     combo);
  426.       gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), title);
  427.       gtk_signal_handler_unblock_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry),
  428.                       combo);
  429.     }
  430.  
  431.   update_toolbar (page);
  432. }
  433.  
  434. static gint
  435. load_page (HelpPage *source_page,
  436.        HelpPage *dest_page,
  437.        gchar    *ref,      
  438.        gint      pos,
  439.        gboolean  add_to_queue,
  440.        gboolean  add_to_history)
  441. {
  442.   GString  *file_contents;
  443.   FILE     *afile = NULL;
  444.   gchar     aline[1024];
  445.   gchar    *old_dir;
  446.   gchar    *new_dir, *new_base;
  447.   gchar    *new_ref;
  448.   gchar    *anchor;
  449.   gboolean  page_valid  = FALSE;
  450.  
  451.   g_return_val_if_fail (ref != NULL &&  
  452.                         source_page != NULL && dest_page != NULL, FALSE);
  453.  
  454.   old_dir  = g_dirname (source_page->current_ref);
  455.   new_dir  = g_dirname (ref);
  456.   new_base = g_basename (ref);
  457.  
  458.   /* return value is intentionally ignored */
  459.   chdir (old_dir);
  460.  
  461.   file_contents = g_string_new (NULL);
  462.  
  463.   if (chdir (new_dir) == -1)
  464.     {
  465.       if (g_path_is_absolute (ref))
  466.     new_ref = g_strdup (ref);
  467.       else
  468.     new_ref = g_strconcat (old_dir, G_DIR_SEPARATOR_S, ref, NULL);
  469.  
  470.       g_string_sprintf (file_contents, gettext (dir_not_found_format_string),
  471.             eek_png_tag, new_dir, new_ref);
  472.       html_source (dest_page, new_ref, 0, file_contents->str, add_to_queue, FALSE);
  473.  
  474.       goto FINISH;
  475.     }
  476.  
  477.   g_free (new_dir);
  478.   new_dir = g_get_current_dir ();
  479.  
  480.   new_ref = g_strconcat (new_dir, G_DIR_SEPARATOR_S, new_base, NULL);
  481.  
  482.   if (strcmp (dest_page->current_ref, new_ref) == 0)
  483.     {
  484.       gtk_xmhtml_set_topline (GTK_XMHTML (dest_page->html), pos);
  485.  
  486.       if (add_to_queue)
  487.     queue_add (dest_page->queue, new_ref, pos);
  488.  
  489.       goto FINISH;
  490.     }
  491.  
  492.   /*
  493.    *  handle basename like: filename.html#11111 -> filename.html
  494.    */
  495.   anchor = strchr (new_base, '#');
  496.   if (anchor)
  497.     {
  498.       *anchor = '\0';
  499.       anchor++;
  500.     }
  501.  
  502.   afile = fopen (new_base, "rt");
  503.  
  504.   if (afile != NULL)
  505.     {
  506.       while (fgets (aline, sizeof (aline), afile))
  507.     file_contents = g_string_append (file_contents, aline);
  508.       fclose (afile);
  509.     }
  510.  
  511.   if (strlen (file_contents->str) <= 0)
  512.     {
  513.       chdir (old_dir);
  514.       g_string_sprintf (file_contents, gettext (doc_not_found_format_string),
  515.             eek_png_tag, ref);
  516.     }
  517.   else
  518.     page_valid = TRUE;
  519.  
  520.   html_source (dest_page, new_ref, 0, file_contents->str, 
  521.            add_to_queue, add_to_history && page_valid);
  522.  
  523.   if (anchor)
  524.     jump_to_anchor (current_page, anchor);
  525.  
  526.  FINISH:
  527.  
  528.   g_free (dest_page->current_ref);
  529.   dest_page->current_ref = new_ref;
  530.  
  531.   g_string_free (file_contents, TRUE);
  532.   g_free (old_dir);
  533.   g_free (new_dir);
  534.  
  535.   gtk_notebook_set_page (GTK_NOTEBOOK (notebook), dest_page->index);
  536.  
  537.   return (page_valid);
  538. }
  539.  
  540. static void
  541. xmhtml_activate (GtkWidget *html,
  542.          gpointer   data)
  543. {
  544.   XmHTMLAnchorCallbackStruct *cbs = (XmHTMLAnchorCallbackStruct *) data;
  545.   GimpParam *return_vals;
  546.   gint       nreturn_vals;
  547.  
  548.   switch (cbs->url_type)
  549.     {
  550.     case URL_JUMP:
  551.       jump_to_anchor (current_page, cbs->href);
  552.       break;
  553.  
  554.     case URL_FILE_LOCAL:
  555.       load_page (current_page, &pages[HELP], cbs->href, 0, TRUE, TRUE);
  556.       break;
  557.  
  558.     default:
  559.       /*  try to call netscape through the web_browser interface */
  560.       return_vals = gimp_run_procedure ("extension_web_browser",
  561.                     &nreturn_vals,
  562.                     GIMP_PDB_INT32,  GIMP_RUN_NONINTERACTIVE,
  563.                     GIMP_PDB_STRING, cbs->href,
  564.                     GIMP_PDB_INT32,  FALSE,
  565.                     GIMP_PDB_END);
  566.        gimp_destroy_params (return_vals, nreturn_vals);
  567.       break;
  568.     }
  569. }
  570.  
  571. static void 
  572. notebook_switch_callback (GtkNotebook     *notebook,
  573.               GtkNotebookPage *page,
  574.               gint             page_num,
  575.               gpointer         user_data)
  576. {
  577.   GtkXmHTML *html;
  578.   gint       i;
  579.   GList     *children;
  580.  
  581.   g_return_if_fail (page_num >= 0 && page_num < 3);
  582.  
  583.   html = GTK_XMHTML (current_page->html);
  584.  
  585.   /*  The html widget fails to do the following by itself  */
  586.  
  587.   GTK_WIDGET_UNSET_FLAGS (html->html.work_area, GTK_MAPPED);
  588.   GTK_WIDGET_UNSET_FLAGS (html->html.vsb, GTK_MAPPED);
  589.   GTK_WIDGET_UNSET_FLAGS (html->html.hsb, GTK_MAPPED);
  590.  
  591.   /*  Frames  */
  592.   for (i = 0; i < html->html.nframes; i++)
  593.     GTK_WIDGET_UNSET_FLAGS (html->html.frames[i]->frame, GTK_MAPPED);
  594.  
  595.   /*  Form widgets  */
  596.   for (children = html->children; children; children = children->next)
  597.     GTK_WIDGET_UNSET_FLAGS (children->data, GTK_MAPPED);
  598.  
  599.   /*  Set the new page  */
  600.   current_page = &pages[page_num];
  601. }
  602.  
  603. static void 
  604. notebook_switch_after_callback (GtkNotebook     *notebook,
  605.                 GtkNotebookPage *page,
  606.                 gint             page_num,
  607.                 gpointer         user_data)
  608. {
  609.   GtkAccelGroup *accel_group = gtk_accel_group_get_default ();
  610.  
  611.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  612.                   "page_up", accel_group,
  613.                   'b', 0, 0);
  614.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  615.                   "page_down", accel_group,
  616.                   ' ', 0, 0);
  617.  
  618.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  619.                   "page_up", accel_group,
  620.                   GDK_Page_Up, 0, 0);
  621.   gtk_widget_add_accelerator (GTK_XMHTML (current_page->html)->html.vsb,
  622.                   "page_down", accel_group,
  623.                   GDK_Page_Down, 0, 0);
  624.  
  625.   update_toolbar (current_page);
  626. }
  627.  
  628.  
  629. static gint
  630. notebook_label_button_press_callback (GtkWidget *widget,
  631.                       GdkEvent  *event,
  632.                       gpointer   data)
  633. {
  634.   guint i = GPOINTER_TO_UINT (data);
  635.  
  636.   if (current_page != &pages[i])
  637.     gtk_notebook_set_page (GTK_NOTEBOOK (notebook), i);
  638.   
  639.   return TRUE;
  640. }
  641.  
  642. static void
  643. combo_drag_begin (GtkWidget *widget,
  644.           gpointer   data)
  645. }
  646.  
  647. static void
  648. combo_drag_handle (GtkWidget        *widget, 
  649.            GdkDragContext   *context,
  650.            GtkSelectionData *selection_data,
  651.            guint             info,
  652.            guint             time,
  653.            gpointer          data)
  654. {
  655.   HelpPage *page = (HelpPage*)data;
  656.  
  657.   if (page->current_ref != NULL)
  658.     {
  659.       gtk_selection_data_set (selection_data,
  660.                   selection_data->target,
  661.                   8, 
  662.                   page->current_ref, 
  663.                   strlen (page->current_ref));
  664.     }
  665. }
  666.  
  667. static void
  668. page_up_callback (GtkWidget *widget,
  669.           GtkWidget *html)
  670. {
  671.   GtkAdjustment *adj;
  672.  
  673.   adj = GTK_ADJUSTMENT (GTK_XMHTML (html)->vsba);
  674.   gtk_adjustment_set_value (adj, adj->value - (adj->page_size));
  675. }
  676.  
  677. static void
  678. page_down_callback (GtkWidget *widget,
  679.             GtkWidget *html)
  680. {
  681.   GtkAdjustment *adj;
  682.  
  683.   adj = GTK_ADJUSTMENT (GTK_XMHTML (html)->vsba);
  684.   gtk_adjustment_set_value (adj, adj->value + (adj->page_size));
  685. }
  686.  
  687. static gint
  688. wheel_callback (GtkWidget      *widget,
  689.         GdkEventButton *bevent,
  690.         GtkWidget      *html)
  691. {
  692.   GtkAdjustment *adj;
  693.   gfloat new_value;
  694.  
  695.   if (! GTK_XMHTML (html)->html.needs_vsb)
  696.     return FALSE;
  697.  
  698.   adj = GTK_ADJUSTMENT (GTK_XMHTML (html)->vsba);
  699.  
  700.   switch (bevent->button)
  701.     {
  702.     case 4:
  703.       new_value = adj->value - adj->page_increment / 2;
  704.       break;
  705.  
  706.     case 5:
  707.       new_value = adj->value + adj->page_increment / 2;
  708.       break;
  709.  
  710.     default:
  711.       return FALSE;
  712.     }
  713.  
  714.   new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size);
  715.   gtk_adjustment_set_value (adj, new_value);
  716.  
  717.   return TRUE;
  718. }
  719.  
  720. static gint
  721. set_initial_history (gpointer data)
  722. {
  723.   gint   add_to_history = GPOINTER_TO_INT (data);
  724.   gchar *title;
  725.  
  726.   title = XmHTMLGetTitle (pages[HELP].html);
  727.   if (add_to_history)
  728.     history_add (pages[HELP].current_ref, title);
  729.  
  730.   gtk_signal_handler_block_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  731.   gtk_entry_set_text (GTK_ENTRY (GTK_COMBO (combo)->entry), title);
  732.   gtk_signal_handler_unblock_by_data (GTK_OBJECT (GTK_COMBO (combo)->entry), combo);
  733.  
  734.   return FALSE;
  735. }
  736.  
  737. gboolean
  738. open_browser_dialog (gchar *help_path,
  739.              gchar *locale,
  740.              gchar *help_file)
  741. {
  742.   GtkWidget *window;
  743.   GtkWidget *vbox, *hbox, *bbox, *html_box;
  744.   GtkWidget *button;
  745.   GtkWidget *title;
  746.   GtkWidget *drag_source;
  747.   GtkWidget *label;
  748.  
  749.   gchar   *initial_dir;
  750.   gchar   *initial_ref;
  751.   gchar   *root_dir;
  752.   gchar   *eek_png_path;
  753.   gint     success;
  754.   guint    i;
  755.  
  756.   gimp_ui_init ("helpbrowser", TRUE);
  757.  
  758.   root_dir = g_strdup (gimp_help_root);
  759.  
  760.   if (chdir (root_dir) == -1)
  761.     {
  762.       g_message (_("GIMP Help Browser Error.\n\n"
  763.            "Couldn't find my root html directory.\n"
  764.            "(%s)"), root_dir);
  765.       return FALSE;
  766.     }
  767.  
  768.   eek_png_path = g_strconcat (root_dir, G_DIR_SEPARATOR_S,
  769.                   "images", G_DIR_SEPARATOR_S,
  770.                   "eek.png", NULL);
  771.   if (access (eek_png_path, R_OK) == 0)
  772.     eek_png_tag = g_strdup_printf ("<img src=\"%s\">", eek_png_path);
  773.  
  774.   g_free (eek_png_path);
  775.  
  776.   if (chdir (help_path) == -1)
  777.     {
  778.       g_message (_("GIMP Help Browser Error.\n\n"
  779.            "Couldn't find my root html directory.\n"
  780.            "(%s)"), help_path);
  781.       return FALSE;
  782.     }
  783.  
  784.   initial_dir = g_get_current_dir ();
  785.  
  786.   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  787.   gtk_signal_connect (GTK_OBJECT (window), "delete_event",
  788.               GTK_SIGNAL_FUNC (close_callback),
  789.               NULL);
  790.   gtk_signal_connect (GTK_OBJECT (window), "destroy",
  791.               GTK_SIGNAL_FUNC (close_callback),
  792.               NULL);
  793.   gtk_window_set_wmclass (GTK_WINDOW (window), "helpbrowser", "Gimp");
  794.   gtk_window_set_title (GTK_WINDOW (window), _("GIMP Help Browser"));
  795.  
  796.   gimp_help_connect_help_accel (window, gimp_standard_help_func,
  797.                 "dialogs/help.html");
  798.  
  799.   vbox = gtk_vbox_new (FALSE, 0);
  800.   gtk_container_add (GTK_CONTAINER (window), vbox);
  801.  
  802.   hbox = gtk_hbox_new (FALSE, 0);
  803.   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  804.  
  805.   bbox = gtk_hbutton_box_new ();
  806.   gtk_button_box_set_spacing (GTK_BUTTON_BOX (bbox), 0);
  807.   gtk_box_pack_start (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
  808.  
  809.   back_button = gimp_pixmap_button_new (back_xpm, _("Back"));
  810.   gtk_button_set_relief (GTK_BUTTON (back_button), GTK_RELIEF_NONE);
  811.   gtk_container_add (GTK_CONTAINER (bbox), back_button);
  812.   gtk_widget_set_sensitive (GTK_WIDGET (back_button), FALSE);
  813.   gtk_signal_connect (GTK_OBJECT (back_button), "clicked",
  814.               GTK_SIGNAL_FUNC (back_callback),
  815.               NULL);
  816.   gtk_widget_show (back_button);
  817.  
  818.   forward_button = gimp_pixmap_button_new (forward_xpm, _("Forward"));
  819.   gtk_button_set_relief (GTK_BUTTON (forward_button), GTK_RELIEF_NONE);
  820.   gtk_container_add (GTK_CONTAINER (bbox), forward_button);
  821.   gtk_widget_set_sensitive (GTK_WIDGET (forward_button), FALSE);
  822.   gtk_signal_connect (GTK_OBJECT (forward_button), "clicked",
  823.               GTK_SIGNAL_FUNC (forward_callback),
  824.               NULL);
  825.   gtk_widget_show (forward_button);
  826.  
  827.   gtk_widget_show (bbox);
  828.  
  829.   bbox = gtk_hbutton_box_new ();
  830.   gtk_box_pack_end (GTK_BOX (hbox), bbox, FALSE, FALSE, 0);
  831.  
  832.   button = gtk_button_new_with_label (_("Close"));
  833.   gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE);
  834.   gtk_container_add (GTK_CONTAINER (bbox), button);
  835.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  836.                GTK_SIGNAL_FUNC (close_callback),
  837.               NULL);
  838.   gtk_widget_show (button);
  839.  
  840.   gtk_widget_show (bbox);
  841.   gtk_widget_show (hbox);
  842.  
  843.   notebook = gtk_notebook_new ();
  844.   gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP);
  845.   gtk_notebook_set_tab_vborder (GTK_NOTEBOOK (notebook), 0);
  846.   gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0);
  847.  
  848.   for (i = 0; i < 3; i++)
  849.     {
  850.       static guint page_up_signal = 0;
  851.       static guint page_down_signal = 0;
  852.  
  853.       pages[i].index       = i;
  854.       pages[i].html        = gtk_xmhtml_new ();
  855.       pages[i].queue       = queue_new ();
  856.  
  857.       gtk_xmhtml_set_anchor_underline_type (GTK_XMHTML (pages[i].html),
  858.                         GTK_ANCHOR_SINGLE_LINE);
  859.       gtk_xmhtml_set_anchor_buttons (GTK_XMHTML (pages[i].html), FALSE);
  860.       gtk_widget_set_usize (GTK_WIDGET (pages[i].html), -1, 300);
  861.  
  862.       switch (i)
  863.     {
  864.     case CONTENTS:
  865.     case INDEX:
  866.       pages[i].current_ref = g_strconcat (root_dir, G_DIR_SEPARATOR_S,
  867.                           locale, G_DIR_SEPARATOR_S,
  868.                           ".", NULL);
  869.  
  870.       title = drag_source = gtk_event_box_new ();
  871.       label = gtk_label_new (gettext (pages[i].label));
  872.       gtk_container_add (GTK_CONTAINER (title), label);
  873.       gtk_widget_show (label);
  874.       break;
  875.     case HELP:
  876.       pages[i].current_ref = g_strconcat (initial_dir, G_DIR_SEPARATOR_S,
  877.                           locale, G_DIR_SEPARATOR_S,
  878.                           ".", NULL);
  879.  
  880.       title = combo = gtk_combo_new ();
  881.       drag_source = GTK_COMBO (combo)->entry;
  882.       gtk_widget_set_usize (GTK_WIDGET (combo), 300, -1);
  883.       gtk_entry_set_editable (GTK_ENTRY (GTK_COMBO (combo)->entry), FALSE); 
  884.       gtk_combo_set_use_arrows (GTK_COMBO (combo), TRUE);
  885.       gtk_signal_connect (GTK_OBJECT (GTK_COMBO (combo)->entry), 
  886.                   "changed",
  887.                   GTK_SIGNAL_FUNC (entry_changed_callback), 
  888.                   combo);
  889.       gtk_signal_connect (GTK_OBJECT (GTK_WIDGET (GTK_COMBO (combo)->entry)), 
  890.                   "button-press-event",
  891.                   GTK_SIGNAL_FUNC (entry_button_press_callback), 
  892.                   NULL);
  893.       gtk_widget_show (combo);
  894.       break;
  895.     default:
  896.       title = drag_source = NULL;     /* to please the compiler */
  897.       break;
  898.     }      
  899.  
  900.       /*  connect to the button_press signal to make notebook switching work */ 
  901.       gtk_signal_connect (GTK_OBJECT (title), "button_press_event",
  902.               GTK_SIGNAL_FUNC (notebook_label_button_press_callback), 
  903.               GUINT_TO_POINTER (i));
  904.  
  905.       /*  dnd source  */
  906.       gtk_drag_source_set (GTK_WIDGET (drag_source),
  907.                GDK_BUTTON1_MASK,
  908.                help_dnd_target_table, n_help_dnd_targets, 
  909.                GDK_ACTION_MOVE | GDK_ACTION_COPY);
  910.       gtk_signal_connect (GTK_OBJECT (drag_source), "drag_begin",
  911.               GTK_SIGNAL_FUNC (combo_drag_begin),
  912.               &pages[i]);
  913.       gtk_signal_connect (GTK_OBJECT (drag_source), "drag_data_get",
  914.               GTK_SIGNAL_FUNC (combo_drag_handle),
  915.               &pages[i]);
  916.  
  917.       html_box = gtk_vbox_new (FALSE, 0);
  918.       gtk_container_add (GTK_CONTAINER (html_box), pages[i].html);
  919.  
  920.       gtk_notebook_append_page (GTK_NOTEBOOK (notebook), html_box, title);
  921.       gtk_widget_show (title);
  922.  
  923.       if (i == HELP && help_file)
  924.     {
  925.       initial_ref = g_strconcat (initial_dir, G_DIR_SEPARATOR_S,
  926.                      locale, G_DIR_SEPARATOR_S,
  927.                      help_file, NULL);
  928.     }
  929.       else
  930.     {
  931.       initial_ref = g_strconcat (root_dir, G_DIR_SEPARATOR_S,
  932.                      locale, G_DIR_SEPARATOR_S,
  933.                      pages[i].home, NULL);
  934.     }
  935.  
  936.       success = load_page (&pages[i], &pages[i], initial_ref, 0, TRUE, FALSE);
  937.       g_free (initial_ref);
  938.  
  939.       gtk_widget_show (pages[i].html);
  940.       gtk_widget_show (html_box);
  941.       gtk_signal_connect (GTK_OBJECT (pages[i].html), "activate",
  942.               (GtkSignalFunc) xmhtml_activate,
  943.               &pages[i]);
  944.  
  945.       if (! page_up_signal)
  946.     {
  947.       page_up_signal = gtk_object_class_user_signal_new 
  948.         (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb)->klass,
  949.          "page_up",
  950.          GTK_RUN_FIRST,
  951.          gtk_marshal_NONE__NONE,
  952.          GTK_TYPE_NONE, 0);
  953.       page_down_signal = gtk_object_class_user_signal_new
  954.         (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb)->klass,
  955.          "page_down",
  956.          GTK_RUN_FIRST,
  957.          gtk_marshal_NONE__NONE,
  958.          GTK_TYPE_NONE, 0);
  959.     }
  960.  
  961.       gtk_signal_connect (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb),
  962.               "page_up",
  963.               GTK_SIGNAL_FUNC (page_up_callback),
  964.               pages[i].html);
  965.       gtk_signal_connect (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.vsb), 
  966.               "page_down",
  967.               GTK_SIGNAL_FUNC (page_down_callback),
  968.               pages[i].html);
  969.  
  970.       gtk_signal_connect (GTK_OBJECT (GTK_XMHTML (pages[i].html)->html.work_area),
  971.               "button_press_event",
  972.               GTK_SIGNAL_FUNC (wheel_callback),
  973.               pages[i].html);
  974.     }
  975.  
  976.   g_free (root_dir);
  977.  
  978.   gtk_signal_connect (GTK_OBJECT (notebook), "switch-page",
  979.               GTK_SIGNAL_FUNC (notebook_switch_callback),
  980.               NULL);
  981.   gtk_signal_connect_after (GTK_OBJECT (notebook), "switch-page",
  982.                 GTK_SIGNAL_FUNC (notebook_switch_after_callback),
  983.                 NULL);
  984.  
  985.   gtk_notebook_set_page (GTK_NOTEBOOK (notebook), HELP);
  986.   gtk_widget_show (notebook);
  987.  
  988.   gtk_widget_show (vbox);
  989.   gtk_widget_show (window);
  990.  
  991.   gtk_idle_add ((GtkFunction) set_initial_history, GINT_TO_POINTER (success));
  992.  
  993.   g_free (initial_dir);
  994.  
  995.   return TRUE;
  996. }
  997.  
  998. static gint
  999. idle_load_page (gpointer data)
  1000. {
  1001.   gchar *path = data;
  1002.  
  1003.   load_page (&pages[HELP], &pages[HELP], path, 0, TRUE, TRUE);
  1004.   g_free (path);
  1005.  
  1006.   return FALSE;
  1007. }
  1008.  
  1009. static void
  1010. run_temp_proc (gchar      *name,
  1011.            gint        nparams,
  1012.            GimpParam  *param,
  1013.            gint       *nreturn_vals,
  1014.            GimpParam **return_vals)
  1015. {
  1016.   static GimpParam  values[1];
  1017.   GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  1018.   gchar *help_path;
  1019.   gchar *locale;
  1020.   gchar *help_file;
  1021.   gchar *path;
  1022.  
  1023.   /*  set default values  */
  1024.   help_path = g_strdup (gimp_help_root);
  1025.   locale    = g_strdup ("C");
  1026.   help_file = g_strdup ("introduction.html");
  1027.  
  1028.   /*  Make sure all the arguments are there!  */
  1029.   if (nparams == 3)
  1030.     {
  1031.       if (param[0].data.d_string && strlen (param[0].data.d_string))
  1032.     {
  1033.       g_free (help_path);
  1034.       help_path = g_strdup (param[0].data.d_string);
  1035.       g_strdelimit (help_path, "/", G_DIR_SEPARATOR);
  1036.     }
  1037.       if (param[1].data.d_string && strlen (param[1].data.d_string))
  1038.     {
  1039.       g_free (locale);
  1040.       locale    = g_strdup (param[1].data.d_string);
  1041.     }
  1042.       if (param[2].data.d_string && strlen (param[2].data.d_string))
  1043.     {
  1044.       g_free (help_file);
  1045.       help_file = g_strdup (param[2].data.d_string);
  1046.       g_strdelimit (help_file, "/", G_DIR_SEPARATOR);
  1047.     }
  1048.     }
  1049.  
  1050.   path = g_strconcat (help_path, G_DIR_SEPARATOR_S, 
  1051.               locale, G_DIR_SEPARATOR_S,
  1052.               help_file, NULL);
  1053.  
  1054.   g_free (help_path);
  1055.   g_free (locale);
  1056.   g_free (help_file);
  1057.  
  1058.   gtk_idle_add (idle_load_page, path);
  1059.  
  1060.   *nreturn_vals = 1;
  1061.   *return_vals = values;
  1062.  
  1063.   values[0].type = GIMP_PDB_STATUS;
  1064.   values[0].data.d_status = status;
  1065. }
  1066.  
  1067. /*  from libgimp/gimp.c  */
  1068. void
  1069. gimp_run_temp (void);
  1070.  
  1071. static gboolean
  1072. input_callback (GIOChannel   *channel,
  1073.                 GIOCondition  condition,
  1074.                 gpointer      data)
  1075. {
  1076.   /* We have some data in the wire - read it */
  1077.   /* The below will only ever run a single proc */
  1078.   gimp_run_temp ();
  1079.  
  1080.   return TRUE;
  1081. }
  1082.  
  1083. static void
  1084. install_temp_proc (void)
  1085. {
  1086.   static GimpParamDef args[] =
  1087.   {
  1088.     { GIMP_PDB_STRING, "help_path", "" },
  1089.     { GIMP_PDB_STRING, "locale",    "Langusge to use" },
  1090.     { GIMP_PDB_STRING, "help_file", "Path of a local document to open. "
  1091.                                     "Can be relative to GIMP_HELP_PATH." }
  1092.   };
  1093.   static gint nargs = sizeof (args) / sizeof (args[0]);
  1094.  
  1095.   gimp_install_temp_proc (GIMP_HELP_TEMP_EXT_NAME,
  1096.               "DON'T USE THIS ONE",
  1097.               "(Temporary procedure)",
  1098.               "Sven Neumann <sven@gimp.org>, "
  1099.               "Michael Natterer <mitschel@cs.tu-berlin.de>",
  1100.               "Sven Neumann & Michael Natterer",
  1101.               "1999",
  1102.               NULL,
  1103.               "",
  1104.               GIMP_TEMPORARY,
  1105.               nargs, 0,
  1106.               args, NULL,
  1107.               run_temp_proc);
  1108.  
  1109.   /* Tie into the gdk input function */
  1110.   g_io_add_watch (_readchannel, G_IO_IN | G_IO_PRI, input_callback, NULL);
  1111.  
  1112.   temp_proc_installed = TRUE;
  1113. }
  1114.  
  1115. static gboolean
  1116. open_url (gchar *help_path,
  1117.       gchar *locale,
  1118.       gchar *help_file)
  1119. {
  1120.   if (! open_browser_dialog (help_path, locale, help_file))
  1121.     return FALSE;
  1122.  
  1123.   install_temp_proc ();
  1124.   gtk_main ();
  1125.  
  1126.   return TRUE;
  1127. }
  1128.  
  1129. MAIN ()
  1130.  
  1131. static void
  1132. query (void)
  1133. {
  1134.   static GimpParamDef args[] =
  1135.   {
  1136.     { GIMP_PDB_INT32,  "run_mode",  "Interactive" },
  1137.     { GIMP_PDB_STRING, "help_path", "" },
  1138.     { GIMP_PDB_STRING, "locale",    "Language to use" },
  1139.     { GIMP_PDB_STRING, "help_file", "Path of a local document to open. "
  1140.                                     "Can be relative to GIMP_HELP_PATH." }
  1141.   };
  1142.   static gint nargs = sizeof (args) / sizeof (args[0]);
  1143.  
  1144.   gimp_install_procedure (GIMP_HELP_EXT_NAME,
  1145.                           "Browse the GIMP help pages",
  1146.                           "A small and simple HTML browser optimzed for "
  1147.               "browsing the GIMP help pages.",
  1148.                           "Sven Neumann <sven@gimp.org>, "
  1149.               "Michael Natterer <mitch@gimp.org>",
  1150.               "Sven Neumann & Michael Natterer",
  1151.                           "1999",
  1152.                           NULL,
  1153.                           "",
  1154.                           GIMP_EXTENSION,
  1155.                           nargs, 0,
  1156.                           args, NULL);
  1157. }
  1158.  
  1159. static void
  1160. run (gchar      *name,
  1161.      gint        nparams,
  1162.      GimpParam  *param,
  1163.      gint       *nreturn_vals,
  1164.      GimpParam **return_vals)
  1165. {
  1166.   static GimpParam  values[1];
  1167.   GimpRunModeType   run_mode;
  1168.   GimpPDBStatusType status = GIMP_PDB_SUCCESS;
  1169.   gchar *env_root_dir = NULL;
  1170.   gchar *help_path    = NULL;
  1171.   gchar *locale       = NULL;
  1172.   gchar *help_file    = NULL;
  1173.  
  1174.   run_mode = param[0].data.d_int32;
  1175.  
  1176.   values[0].type = GIMP_PDB_STATUS;
  1177.   values[0].data.d_status = status;
  1178.  
  1179.   *nreturn_vals = 1;
  1180.   *return_vals = values;
  1181.  
  1182.   INIT_I18N_UI ();
  1183.  
  1184.   if (strcmp (name, GIMP_HELP_EXT_NAME) == 0)
  1185.     {
  1186.       switch (run_mode)
  1187.         {
  1188.         case GIMP_RUN_INTERACTIVE:
  1189.         case GIMP_RUN_NONINTERACTIVE:
  1190.     case GIMP_RUN_WITH_LAST_VALS:
  1191.       /*  set default values  */
  1192.       env_root_dir = g_getenv ("GIMP_HELP_ROOT");
  1193.  
  1194.       if (env_root_dir)
  1195.         {
  1196.           if (chdir (env_root_dir) == -1)
  1197.         {
  1198.           g_message (_("GIMP Help Browser Error.\n\n"
  1199.                    "Couldn't find GIMP_HELP_ROOT html directory.\n"
  1200.                    "(%s)"), env_root_dir);
  1201.  
  1202.           status = GIMP_PDB_EXECUTION_ERROR;
  1203.           break;
  1204.         }
  1205.  
  1206.           gimp_help_root = g_strdup (env_root_dir);
  1207.         }
  1208.       else
  1209.         {
  1210.           gimp_help_root = g_strconcat (gimp_data_directory(),
  1211.                         G_DIR_SEPARATOR_S, 
  1212.                         GIMP_HELP_PREFIX, NULL);
  1213.         }
  1214.  
  1215.       help_path = g_strdup (gimp_help_root);
  1216.       locale    = g_strdup ("C");
  1217.       help_file = g_strdup ("introduction.html");
  1218.       
  1219.       /*  Make sure all the arguments are there!  */
  1220.       if (nparams == 4)
  1221.         {
  1222.           if (param[1].data.d_string && strlen (param[1].data.d_string))
  1223.         {
  1224.           g_free (help_path);
  1225.           help_path = g_strdup (param[1].data.d_string);
  1226.           g_strdelimit (help_path, "/", G_DIR_SEPARATOR);
  1227.         }
  1228.           if (param[2].data.d_string && strlen (param[2].data.d_string))
  1229.         {
  1230.           g_free (locale);
  1231.           locale = g_strdup (param[2].data.d_string);
  1232.         }
  1233.           if (param[3].data.d_string && strlen (param[3].data.d_string))
  1234.         {
  1235.           g_free (help_file);
  1236.           help_file = g_strdup (param[3].data.d_string);
  1237.           g_strdelimit (help_file, "/", G_DIR_SEPARATOR);
  1238.         }
  1239.         }
  1240.           break;
  1241.  
  1242.         default:
  1243.       status = GIMP_PDB_CALLING_ERROR;
  1244.           break;
  1245.         }
  1246.  
  1247.       if (status == GIMP_PDB_SUCCESS)
  1248.         {
  1249.              if (!open_url (help_path, locale, help_file))
  1250.             values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
  1251.       else
  1252.         values[0].data.d_status = GIMP_PDB_SUCCESS;
  1253.  
  1254.       g_free (help_path);
  1255.       g_free (locale);
  1256.       g_free (help_file);
  1257.         }
  1258.       else
  1259.         values[0].data.d_status = status;
  1260.     }
  1261.   else
  1262.     values[0].data.d_status = GIMP_PDB_CALLING_ERROR;
  1263. }
  1264.